namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Diagnostics;
  using System.Collections.Generic;
  using System.Text;

  /// <summary>
  /// HintNameEntry
  /// </summary>
  public struct HintNameEntry
  {
    #region Statics
    public static readonly HintNameEntry Terminator = it();
    public static HintNameEntry it()
    {
      return new HintNameEntry();
    }
    public static HintNameEntry of(UInt16 hint, string name)
    {
      return new HintNameEntry(hint, name);
    }
    public static HintNameEntry from(BinaryReader reader)
    {
      return it().Read(reader);
    }
    #endregion
    #region Consts
    public const UInt32 TermintorName = 0;
    #endregion
    #region Fields
    public UInt16 Hint;
    public string Name; //should be padded to even boundary
    #endregion
    #region Properties
    public bool IsValid
    {
      get
      {
        return !string.IsNullOrEmpty(this.Name);
      }
    }
    #endregion
    #region Constructors
    public HintNameEntry(UInt16 hint, string name)
    {
      this.Hint = hint;
      this.Name = name;
    }
    #endregion
    #region Methods
    #region Internals
    public HintNameEntry Read(BinaryReader reader)
    {
      if (reader != null)
      {
        Int64 current = Utils.GetPosition(reader);
        if (current % 2 != 0)
        {
          Utils.Read8(reader);
        }
        this.Hint = Utils.Read16(reader);
        this.Name = Utils.ReadName(reader, Encoding.ASCII);
      }
      return this;
    }
    public HintNameEntry Write(BinaryWriter writer)
    {
      if (writer != null)
      {
        Utils.Write16(writer,this.Hint);
        if (this.IsValid)
        {
          Utils.WriteName(writer, this.Name, Encoding.ASCII);
          Int64 current = Utils.GetPosition(writer);
          if (current % 2 != 0)
          {
            Utils.Write8(writer,(Byte)'\0');
          }
        }
        else
        {
          Utils.Write32(writer, TermintorName);
        }
      }
      return this;
    }
    #endregion
    #endregion
  }
  /// <summary>
  /// ImportLookupType
  /// </summary>
  public enum ImportLookupType:uint
  {
    PE32      = 0,
    PE64      = 1,
    Oridinal  = 2,
  }
  /// <summary>
  /// ImportLookupEntry
  /// </summary>
  /// <remarks>
  /// Respresents a function in a DLL.
  /// </remarks>
  public struct ImportLookupEntry
  {
    #region Statics
    public static readonly ImportLookupEntry Terminator = it();
    public static ImportLookupEntry of(UInt32 entry)
    {
      return it().SetEntry(entry);
    }
    public static ImportLookupEntry of(UInt64 entry)
    {
      return it().SetEntry(entry);
    }
    public static ImportLookupEntry from(BinaryReader reader,bool isPE64)
    {
      return it().Read(reader, isPE64);
    }
    public static ImportLookupEntry it()
    {
      return new ImportLookupEntry();
    }
    #endregion
    #region Consts
    public const UInt32 PE32Mask = 0x7FFFFFFF;
    public const UInt64 PE64Mask = 0x7FFFFFFFFFFFFFFF;
    public const UInt32 PE32Mark = 0x80000000;
    public const UInt64 PE64Mark = 0x8000000000000000;
    public const UInt32 InvalidEntry32 = 0;
    public const UInt64 InvalidEntry64 = 0;
    #endregion
    #region Fields
    public ImportLookupType Type;
    public UInt64 Index;
    public HintNameEntry HintName;
    #endregion
    #region Properties
    public bool IsOrdinalIndex
    {
      get
      {
        return (this.Type & ImportLookupType.Oridinal) != 0;
      }
    }
    public bool IsNameIndex
    {
      get
      {
        return !this.IsOrdinalIndex;
      }
    }
    public bool IsPE32
    {
      get
      {
        return !this.IsPE64;
      }
    }
    public bool IsPE64
    {
      get
      {
        return (this.Type & ImportLookupType.PE64) != 0;
      }
    }
    public bool IsValid
    {
      get
      {
        return this.Index != InvalidEntry64;
      }
    }
    #endregion
    #region Constructors
    #endregion
    #region Methods
    #region Convertions
    public ImportLookupEntry SetEntry(UInt32 entry)
    {
      this.Type = ImportLookupType.PE32;

      if ((entry & PE32Mask) != entry)
      {
        this.Type |= ImportLookupType.Oridinal;
      }
      this.Index = entry & PE32Mask;
      return this;
    }
    public ImportLookupEntry SetEntry(UInt64 entry)
    {
      this.Type = ImportLookupType.PE64;

      if ((entry & PE64Mask) != entry)
      {
        this.Type |= ImportLookupType.Oridinal;
      }

      this.Index = entry & PE64Mask;
      return this;
    }
    public UInt32 GetEntry32()
    {
      UInt32 entry = (UInt32)this.Index;
      if (this.IsOrdinalIndex)
      {
        entry |= PE32Mark;
      }
      return entry;
    }
    public UInt64 GetEntry64()
    {
      UInt64 entry = (UInt64)this.Index;
      if (this.IsOrdinalIndex)
      {
        entry |= PE64Mark;
      }
      return entry;
    }
    #endregion
    #region Read/Write
    public ImportLookupEntry Read(BinaryReader reader, bool isPE64)
    {
      if (reader != null)
      {
        if (isPE64)
        {
          this.SetEntry(Utils.Read64(reader));
        }
        else
        {
          this.SetEntry(Utils.Read32(reader));
        }
      }
      return this;
    }
    public ImportLookupEntry Write(BinaryWriter writer, bool isPE64)
    {
      if (writer != null)
      {
        if (isPE64)
        {
          Utils.Write64(writer, this.IsValid ? this.GetEntry64() : InvalidEntry64);
        }
        else
        {
          Utils.Write32(writer, this.IsValid ? this.GetEntry32() : InvalidEntry32);
        }
      }
      return this;
    }
    #endregion
    #region Internals
    #endregion
    #endregion
  }
  /// <summary>
  /// ImportDirectoryEntry
  /// </summary>
  /// <remarks>
  /// Represents a DLL
  /// </remarks>
  public struct ImportDirectoryEntry
  {
    #region Statics
    public static readonly ImportDirectoryEntry Terminator = it();

    public static ImportDirectoryEntry it()
    {
      return new ImportDirectoryEntry();
    }
    public static ImportDirectoryEntry from(BinaryReader reader, NTHeader header)
    {
      return it().Read(reader, header);
    }
    #endregion
    #region Fields
    public UInt32 ImportLookupTable;
    public UInt32 TimeDateStamp;
    public UInt32 ForwarderChain;
    public UInt32 NameAddress;
    public UInt32 ImportAddressTable;
    public ImportLookupEntry[] ImportLookups;
    public ImportLookupEntry[] ImportAddresses;
    #endregion
    #region Internals
    internal string _Name;
    internal Int64 _StartPosition;
    internal Int64 _EndPosition;
    #endregion
    #region Properties
    public Int64 StartPosition
    {
      get
      {
        return this._StartPosition;
      }
    }
    public Int64 EndPosition
    {
      get
      {
        return this._EndPosition;
      }
    }
    public string Name
    {
      get
      {
        return this._Name;
      }
      set
      {
        this._Name = value;
      }
    }
    public DateTime CreateTime
    {
      get
      {
        return Utils.GetDateTime((Int32)this.TimeDateStamp);
      }
      set
      {
        this.TimeDateStamp = (UInt32)Utils.GetTimeStamp(value);
      }
    }
    public bool IsTerminal
    {
      get
      {
        return 
           this.ImportLookupTable == 0
        && this.TimeDateStamp == 0
        && this.ForwarderChain == 0
        && this.NameAddress == 0
        && this.ImportAddressTable == 0;

      }
    }
    public bool HasLookups
    {
      get
      {
        return this.ImportLookups != null && this.ImportLookups.Length > 0;
      }
    }
    public bool HasAddresses
    {
      get
      {
        return this.ImportAddresses != null && this.ImportAddresses.Length > 0;
      }
    }
    #endregion
    #region Methods
    #region Read/Write
    public ImportDirectoryEntry Read(BinaryReader reader,NTHeader header)
    {
      if (reader != null)
      {
        this._StartPosition = Utils.GetPosition(reader);
        this.ImportLookupTable = Utils.Read32(reader);
        this.TimeDateStamp = Utils.Read32(reader);
        this.ForwarderChain = Utils.Read32(reader);
        this.NameAddress = Utils.Read32(reader);
        this.ImportAddressTable = Utils.Read32(reader);
        if (!this.IsTerminal)
        {
          this._Name = Utils.ReadName(reader, Encoding.ASCII, header.GetFilePointer(this.NameAddress));
          this.ImportLookups = this.ReadEntries(reader, header, this.ImportLookupTable,true);
          this.ImportAddresses = this.ReadEntries(reader, header, this.ImportAddressTable,false);
        }
      }
      return this;
    }
    public ImportDirectoryEntry Write(BinaryWriter writer,NTHeader header)
    {
      if (writer != null)
      {
        Utils.Write32(writer, this.ImportLookupTable);
        Utils.Write32(writer, this.TimeDateStamp);
        Utils.Write32(writer, this.ForwarderChain);
        Utils.Write32(writer, this.NameAddress);
        Utils.Write32(writer, this.ImportAddressTable);

        if (!this.IsTerminal)
        {
          Utils.WriteName(writer, header.GetFilePointer(this.NameAddress), this._Name, Encoding.ASCII);
          this.WriteEntries(writer, header, this.ImportLookupTable, this.ImportLookups,true);
          this.WriteEntries(writer, header, this.ImportAddressTable, this.ImportAddresses,false);
        }
      }
      return this;
    }
    #endregion
    #region Internals
    internal ImportLookupEntry[] ReadEntries(BinaryReader reader, NTHeader header, UInt32 RVA,bool keepPosition)
    {
      ImportLookupEntry[] entries = null;

      if (reader != null)
      {
        Int64 current = Utils.GetPosition(reader);
        UInt32 start = header.GetFilePointer(RVA);
        if (start != 0 && Utils.SetPosition(reader, start))
        {
          List<ImportLookupEntry> lookups = new List<ImportLookupEntry>();

          while (true)
          {
            ImportLookupEntry entry = ImportLookupEntry.from(reader, header.IsPE64);

            if (entry.IsValid)
            {
              lookups.Add(entry);
            }
            else
            {
              break;
            }
          }
          if (lookups.Count > 0)
          {
            entries = lookups.ToArray();
          }
          if (keepPosition)
          {
            this._EndPosition = Utils.GetPosition(reader);
          }
          Utils.SetPosition(reader, current);
        }
      }
      return entries;
    }
    internal ImportLookupEntry[] WriteEntries(BinaryWriter writer, NTHeader header, UInt32 RVA, ImportLookupEntry[] entries, bool keepPosition)
    {
      if (writer != null && entries != null && entries.Length > 0)
      {
        Int64 current = Utils.GetPosition(writer);

        UInt32 start = header.GetFilePointer(RVA);

        if (start != 0 && Utils.SetPosition(writer, start))
        {
          for (UInt32 i = 0; i < entries.Length; i++)
          {
            entries[i].Write(writer, header.IsPE64);
          }

          ImportLookupEntry.Terminator.Write(writer, header.IsPE64);
          if (keepPosition)
          {
            this._EndPosition = Utils.GetPosition(writer);
          }
          Utils.SetPosition(writer, current);
        }
      }
      return entries;
    }
    #endregion
    #endregion
  }
  /// <summary>
  /// Import Directory
  /// </summary>
  public struct ImportDirectory:DataDirectory
  {
    #region Statics
    public static ImportDirectory it()
    {
      return new ImportDirectory();
    }
    public static ImportDirectory from(BinaryReader reader, NTHeader header)
    {
      return (ImportDirectory) it().Read(reader, header);
    }
    #endregion
    #region Fields
    public ImportDirectoryEntry[] ImportEntries;
    public HintNameEntry[] HintNames;
    #endregion
    #region Internal Fields
    internal ImageDataDirectory _Entry;
    #endregion
    #region Properties
    public ImageDataDirectory Entry
    {
      get
      {
        return this._Entry;
      }
      set
      {
        this._Entry = value;
      }
    }
    public bool HasImportEntries
    {
      get
      {
        return this.ImportEntries != null && this.ImportEntries.Length > 0;
      }
    }
    #endregion
    #region Methods
    #region Convertions
    #endregion
    #region Write/Read
    public DataDirectory Read(BinaryReader reader, NTHeader header)
    {
      if (reader != null)
      {
        this._Entry = header[DirectoryEntries.Import];

        Int64 current = Utils.GetPosition(reader);

        UInt32 start = header.GetFilePointer(this.Entry.VirtualAddress);

        if (start != 0 && Utils.SetPosition(reader, start))
        {
          Int64 lastPosition = this.ReadImportEntries(reader,header);

          this.HintNames = this.ReadHintNames(reader, lastPosition);

          Utils.SetPosition(reader, current);
        }
      }
      return this;
    }
    public DataDirectory Write(BinaryWriter writer, NTHeader header)
    {
      if (writer != null)
      {
        header[DirectoryEntries.Import] = this._Entry;

        Int64 current = Utils.GetPosition(writer);

        UInt32 start = header.GetFilePointer(this.Entry.VirtualAddress);

        if (start != 0 && Utils.SetPosition(writer, start))
        {
          Int64 lastPosition = this.WriteImportEntries(writer,header);

          this.WriteHintNames(writer, lastPosition, this.HintNames);

          Utils.SetPosition(writer, current);
        }
      }
      return this;
    }
    #endregion
    #region Internals
    internal HintNameEntry[] ReadHintNames(BinaryReader reader,Int64 filePosition)
    {
      HintNameEntry[] hintNames = null;
      if (reader != null && filePosition!=0)
      {
        Int64 current = Utils.GetPosition(reader);

        if (Utils.SetPosition(reader, filePosition))
        {
          List<HintNameEntry> hintNameList = new List<HintNameEntry>();
          while (true)
          {
            HintNameEntry hintName = HintNameEntry.from(reader);
            if (hintName.IsValid)
            {
              hintNameList.Add(hintName);
            }
            else
            {
              break;
            }
          }
          hintNames = hintNameList.ToArray();

          Utils.SetPosition(reader, current);
        }
      }
      return hintNames;
    }
    internal HintNameEntry[] WriteHintNames(BinaryWriter writer, Int64 filePosition, HintNameEntry[] hintNames)
    {
      if (writer != null && filePosition != 0 && hintNames != null)
      {
        Int64 current = Utils.GetPosition(writer);

        if (Utils.SetPosition(writer, filePosition))
        {
          for (UInt32 i = 0; i < hintNames.Length; i++)
          {
            hintNames[i].Write(writer);
          }
          
          HintNameEntry.Terminator.Write(writer);

          Utils.SetPosition(writer, current);
        }
      }
      return hintNames;
    }
    internal Int64 ReadImportEntries(BinaryReader reader,NTHeader header)
    {
      this.ImportEntries = null;

      Int64 LastPosition = 0;

      List<ImportDirectoryEntry> entries = new List<ImportDirectoryEntry>();

      while (true)
      {
        ImportDirectoryEntry entry = ImportDirectoryEntry.from(reader, header);
        if (!entry.IsTerminal)
        {
          entries.Add(entry);
        }
        else
        {
          break;
        }
      }
      if (entries.Count > 0)
      {
        foreach (ImportDirectoryEntry entry in this.ImportEntries = entries.ToArray())
        {
          if (entry.EndPosition > LastPosition)
          {
            LastPosition = entry.EndPosition;
          }
        }
      }
      return LastPosition;
    }
    internal Int64 WriteImportEntries(BinaryWriter writer, NTHeader header)
    {
      Int64 LastPosition = 0;
      if (writer != null && this.HasImportEntries)
      {
        for (UInt32 i = 0; i < this.ImportEntries.Length; i++)
        {
          this.ImportEntries[i].Write(writer, header);

          if (this.ImportEntries[i].EndPosition > LastPosition)
          {
            LastPosition = this.ImportEntries[i].EndPosition;
          }
        }
        ImportDirectoryEntry.Terminator.Write(writer, header);
      }
      return LastPosition;
    }
    #endregion
    #endregion
  }
}
